#include <jni.h>
#include <string>

#include "AudioChannel.h"
#include "VideoChannel.h"
#include "commons/SafeQueue.h"


AudioChannel *audioChannel = nullptr;
VideoChannel *videoChannel = nullptr;
SafeQueue<RTMPPacket *> *packetQueue = nullptr;

RTMP *rtmp = nullptr;

char *url = nullptr;

volatile bool prepared = false;
volatile bool preparing = false;
volatile bool released = true;
volatile bool releasing = false;
volatile bool pushing = true;

pthread_t pidPush;

bool prepare();

void reconnect();

void _push();

void freeRtmpPacket(RTMPPacket *&packet);

//JNI接口加载完毕
//System.loadLibrary函数被调用时，会触发此方法
extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNI::jvm = vm;
    return JNI_VERSION_1_6;
}

//JNI接口被JVM回收时，会触发此方法
extern "C" JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved) {
    //销毁音视频通道和队列
    DELETE(videoChannel)
    DELETE(audioChannel)
    DELETE(packetQueue)
    //删除Java回调
    delete Pusher::java;
    Pusher::java = nullptr;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_rtmppusher_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1initialize(JNIEnv *env, jobject thiz,
                                                               jstring tag) {
    //设置日志标签
    JNIPrivate::TAG = JNI::toConstChar(tag);
    //记录JNI环境
    JNI::env = env;
    JNI::interface = env->NewGlobalRef(thiz);
    //创建JavaCaller，用于回调Java层方法
    Pusher::java = new JavaCaller();
    //stdio重定向到logcat
    JNI::stdioToLogcat();
    std::cout << "RtmpPusher initialize" << std::endl;

    //创建AudioChannel和VideoChannel
    //AudioChannel和VideoChannel负责将编码后的RTMPPacket存储到PacketQueue中
    //推流线程再从PacketQueue中取出RTMPPacket推给服务器
    packetQueue = new SafeQueue<RTMPPacket *>([](RTMPPacket *&packet) -> void {
        freeRtmpPacket(packet);
    });
    videoChannel = new VideoChannel(packetQueue);
    audioChannel = new AudioChannel();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1prepare(JNIEnv *env, jobject thiz,
                                                            jstring jurl) {
    //jstring地址转char*地址
    const char *constUrl = JNI::toConstChar(jurl);
    url = JNI::toChar(constUrl);
    //开始准备
    preparing = true;
    prepared = false;
    released = false;
    //准备播放环境
    bool ret = prepare();
    packetQueue->setWorking(ret);
    //准备完毕
    prepared = true;
    preparing = false;
    //创建推流线程
    pthread_create(&pidPush, nullptr, [](void *context) -> void * {
        _push();
        packetQueue->setWorking(false);
        return nullptr;
    }, nullptr);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1release(JNIEnv *env, jobject thiz) {
    //开始释放
    releasing = true;
    released = false;
    prepared = false;
    //停止队列工作
    packetQueue->setWorking(false);
    //等待推流结束
    pthread_join(pidPush, nullptr);
    //清空队列中的剩余数据
    packetQueue->clear();
    //销毁RTMP
    if (rtmp) {
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
    }
    //销毁URL
    delete (url);
    //释放完毕
    released = true;
    releasing = false;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1resume(JNIEnv *env, jobject thiz) {
    pushing = true;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1pause(JNIEnv *env, jobject thiz) {
    pushing = false;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1set_1audio_1encode_1info(JNIEnv *env,
                                                                             jobject thiz,
                                                                             jint sample_rate,
                                                                             jint channels) {
    audioChannel->setAudioEncodeInfo(sample_rate, channels);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1set_1video_1encode_1info(JNIEnv *env,
                                                                             jobject thiz,
                                                                             jint width,
                                                                             jint height, jint fps,
                                                                             jint bitrate) {
    videoChannel->setVideoEncodeInfo(width, height, fps, bitrate);
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1get_1input_1samples(JNIEnv *env, jobject thiz) {
    return audioChannel->getInputSamples();
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1push_1audio_1frame(JNIEnv *env, jobject thiz,
                                                                       jbyteArray byteArray) {
    if (!pushing) return;
    if (!packetQueue->isWorking()) return;
    int8_t *bytes = env->GetByteArrayElements(byteArray, new jboolean(true));
    RTMPPacket *rawDataPacket = audioChannel->createRawDataPacket(bytes);
    packetQueue->enqueue(rawDataPacket);
    env->ReleaseByteArrayElements(byteArray, bytes, 0);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1push_1video_1frame(JNIEnv *env, jobject thiz,
                                                                       jbyteArray byteArray) {
    if (!pushing) return;
    if (!packetQueue->isWorking()) return;
    jbyte *bytes = env->GetByteArrayElements(byteArray, new jboolean(true));
//    RTMPPacket * rtmpPacket = videoChannel->createRawDataPacket(bytes);
    videoChannel->encodeNV21ToRtmp(bytes);
    env->ReleaseByteArrayElements(byteArray, bytes, 0);
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1is_1prepared(JNIEnv *env, jobject thiz) {
    return prepared;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1is_1released(JNIEnv *env, jobject thiz) {
    return released;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1is_1preparing(JNIEnv *env, jobject thiz) {
    return preparing;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1is_1releasing(JNIEnv *env, jobject thiz) {
    return releasing;
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_rtmppusher_push_RtmpPusher_native_1is_1pushing(JNIEnv *env, jobject thiz) {
    return pushing;
}

//准备推流环境
bool prepare() {
    //创建RTMP
    rtmp = RTMP_Alloc();
    if (!rtmp) {
        std::cout << "alloc rtmp fail" << std::endl;
        return false;
    }
    //初始化RTMP
    RTMP_Init(rtmp);
    //设置RTMP地址
    bool ret = RTMP_SetupURL(rtmp, url);
    if (!ret) {
        std::cout << "set rtmp url fail " << url << std::endl;
        return false;
    }
    //设置RTMP超时时间
    rtmp->Link.timeout = 5;
    //将RTMP置为写状态，表示推流，而不是取流
    RTMP_EnableWrite(rtmp);
    //连接RTMP服务器
    ret = RTMP_Connect(rtmp, nullptr);
    if (!ret) {
        std::cout << "connect rtmp server fail " << url << std::endl;
        return false;
    }
    //连接RTMP流
    ret = RTMP_ConnectStream(rtmp, 0);
    if (!ret) {
        std::cout << "connect rtmp stream fail " << url << std::endl;
        return false;
    }
    //记录开始时间
    //帧以相当于开始时间的时间差作为时间戳
    uint32_t startTime = RTMP_GetTime();
    audioChannel->setStartTime(startTime);
    videoChannel->setStartTime(startTime);
    return true;
}

//重新连接
void reconnect() {
    std::cout << "rtmp reconnecting " << url << std::endl;
    //队列停止工作
    packetQueue->setWorking(false);
    //清空队列中的剩余数据
    packetQueue->clear();
    //销毁RTMP
    if (rtmp) {
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
    }
    //重建RTMP
    bool ret = prepare();
    //队列恢复工作
    packetQueue->setWorking(true);
    //重连成功
    if (ret)
        std::cout << "rtmp reconnected " << url << std::endl;
}

//开始推流线程
void _push() {
    //RTMP音频数据包，是由一个存储AudioSpecificConfig的FlvTag，和若干存储音频裸数据FlvTag组成的
    //推流前首先发送一个存储AudioSpecificConfig的Packet
    RTMPPacket *configPacket = audioChannel->createConfigPacket();
    packetQueue->enqueue(configPacket);
    //不断从队列中取出Packet进行推送
    RTMPPacket *packet = nullptr;
    LOGE("start push...");
    while (!releasing && !released) {
        if (!pushing)
            continue;
        //取出Packet
        packetQueue->dequeue(packet);
        if (!packet)
            continue;
        //打印队列数据量
        int size = packetQueue->size();
        std::cout << "rtmp packetQueue size " << size << std::endl;
        //发送Packet
        packet->m_nInfoField2 = rtmp->m_stream_id;
        bool ret = RTMP_SendPacket(rtmp, packet, 1);
//        LOGE("send packet...");
        freeRtmpPacket(packet);
        if (!ret || !RTMP_IsConnected(rtmp)) {
            //断线重连
            reconnect();
            continue;
        }
    }
    freeRtmpPacket(packet);
}

//释放RTMPPacket
void freeRtmpPacket(RTMPPacket *&packet) {
    if (packet) {
        RTMPPacket_Free(packet);
        delete packet;
        packet = nullptr;
    }
}